#
# GerikSpellsUtils.py
# Utility functions for GerikSpells
#

from CvPythonExtensions import *
from SdToolkitAdvanced import *

gc = CyGlobalContext()

def convertDataListIntoPages(pList, iMaxTargetsPerPage):
    """
    Arguments:
    pList: A list of (iData1, iData2) tuples
    iMaxTargetsPerPage: The maximum amount of targets per page

    Returns:
    A 2D list adhering to the PieMenu requirements.

    This is a function that can be used to easily take a long list
    and partition it into pages for you for use in a PieMenu.
    """
    if (not pList or len(pList) == 0):
        return None

    if (iMaxTargetsPerPage == -1):
        return [pList]
    
    pEndList = []
    pTempList = []
    for iI in range(0, len(pList)):
        # Make a list for each page, adding in the next item
        if (iI % iMaxTargetsPerPage == 0 and iI != 0):
            pEndList.append(pTempList)
            pTempList = []

        pTempList.append(pList[iI])

    # Append the final units into the last page and return
    pEndList.append(pTempList)
    return pEndList

#
# getRangePlotDistanceList
# Returns a list a plots that would be within range of a spell that allows
# for a range of N. Uses this pattern:

# 4 | 4 | 3 | 3 | 3 | 4 | 4
# -------------------------
# 4 | 3 | 2 | 2 | 2 | 3 | 4
# -------------------------
# 3 | 2 | 1 | 1 | 1 | 2 | 3
# -------------------------
# 3 | 2 | 1 | 0 | 1 | 2 | 3
# -------------------------
# 3 | 2 | 1 | 1 | 1 | 2 | 3
# -------------------------
# 4 | 3 | 2 | 2 | 2 | 3 | 4
# -------------------------
# 4 | 4 | 3 | 3 | 3 | 4 | 4
#

def getRangePlotDistanceList(pMiddlePlot, iRange):
    pList = []
    middleX = pMiddlePlot.getX()
    middleY = pMiddlePlot.getY()
    for iX in range(middleX - iRange, middleX + iRange + 1, 1):
        for iY in range(middleY - iRange, middleY + iRange + 1, 1):
            if (plotDistance(middleX, middleY, iX, iY <= iRange) ):
                pList.append( CyMap().plot(iX, iY) )

    return pList

def getRangeStepDistanceList(pMiddlePlot, iRange):
    """
    Returns a list of plots that would be within range of a spell that allows
    for a range of N. Uses this pattern:

    3 | 3 | 3 | 3 | 3 | 3 | 3
    -------------------------
    3 | 2 | 2 | 2 | 2 | 2 | 3
    -------------------------
    3 | 2 | 1 | 1 | 1 | 2 | 3
    -------------------------
    3 | 2 | 1 | 0 | 1 | 2 | 3
    -------------------------
    3 | 2 | 1 | 1 | 1 | 2 | 3
    -------------------------
    3 | 2 | 2 | 2 | 2 | 2 | 3
    -------------------------
    3 | 3 | 3 | 3 | 3 | 3 | 3
    """
    pList = []
    for iX in range(pMiddlePlot.getX()-iRange, pMiddlePlot.getX() + iRange, 1):
        for iY in range(pMiddlePlot.getY() - iRange, pMiddlePlot.getY() + iRange, 1):
            pList.append( CyMap().plot(iX, iY) )

    return pList


def getBuildTurnsLeftWithHelp(pPlot, pGroup, eSpell, iTotalBuildTime, szScriptDataKey):
    """
    getBuildTurnsLeftWithHelp
    A helper class to getBuildTurnsLeft, for use when a group outside those that are currently
    working on the build need to be thrown into the calculation.
    Arguments:
    pPlot: The plot.
    pGroup: The group that should be calculated in as if they were working the plot (even
            though they aren't).
    eSpell: The spell number that relates to this style of build.
    iTotalBuildTime: The build time for the spell's build.
    szScriptDataKey: The name of the string that is being used to store the completion of the
                     build using the SdToolkitAdvanced library.
    """
    iExtraThisTurn = 0
    iExtraEveryTurn = 0
    for pUnit in pGroup.getUnits():
        iExtraThisTurn += pUnit.workRate(False)
        iExtraEveryTurn += pUnit.workRate(True)

    return getBuildTurnsLeft(pPlot, eSpell, iTotalBuildTime, szScriptDataKey, iExtraThisTurn, iExtraEveryTurn)

        
    

def getBuildTurnsLeft(pPlot, eSpell, iTotalBuildTime, szScriptDataKey, iExtraThisTurn=0, iExtraEveryTurn=0):
    """
    getBuildTurnsLeft
    Arguments:
    pPlot: The plot.
    eSpell: The spell number that relates to this style of build.
    iTotalBuildTime: The build time for the spell's build.
    szScriptDataKey: The name of the string that is being used to store the completion of the
                     build using the SdToolkitAdvanced library.
    iExtraThisTurn:   The calculation pretends this extra amount is added this turn.
    iExtraEveryTurn: The calculation pretends this extra amount is added every turn. 

    Returns:
    The number of build turns left to complete a build spawned from a eSpell for a certain
    team. It's assumed that each team has it's own szScriptDataKey.
    """

    iBuildSoFar = 0
    if sdObjectValExists("GerikSpells", pPlot, szScriptDataKey):
        iBuildSoFar = sdObjectGetVal("GerikSpells", pPlot, szScriptDataKey)

    iTurnsLeft = 0
    iBuildRateThisTurn = iExtraThisTurn
    iBuildRateEveryTurn = iExtraEveryTurn


    iNumUnits = pPlot.getNumUnits()
    for iI in range(0, iNumUnits):
        pLoopUnit = pPlot.getUnit(iI)
        if pLoopUnit.getGroup().getSpellNumber() == eSpell:
            if pLoopUnit.canMove():
                iBuildRateThisTurn += pLoopUnit.workRate(False)
            iBuildRateEveryTurn += pLoopUnit.workRate(True)

    if (iBuildRateEveryTurn == 0):
        return 999

    iBuildTime = iTotalBuildTime
    iBuildTime *= max(0, (gc.getTerrainInfo(pPlot.getTerrainType()).getBuildModifier() + 100))
    iBuildTime /= 100

    iBuildTime *= gc.getGameSpeedInfo(gc.getGame().getGameSpeedType()).getBuildPercent();
    iBuildTime /= 100

    iBuildTime *= gc.getEraInfo(gc.getGame().getStartEra()).getBuildPercent() 
    iBuildTime /= 100


    iBuildLeft = max(0, iBuildTime - iBuildSoFar - iBuildRateThisTurn)

    iTurnsLeft = (iBuildLeft / iBuildRateEveryTurn)

    if ((iTurnsLeft * iBuildRateEveryTurn) < iBuildLeft):
        iTurnsLeft += 1

    iTurnsLeft += 1

    return max(1, iTurnsLeft)




#
# combine & uncombine
# Takes two 11-bit integers and combines them into one 22-bit integer.
# Also, takes a 22-bit integer, and splits it into two 11-bit integers.
# This should ONLY be used for saving two integers into iFlags (22-bit
# because 10 bits are being taken up to store the spell #).
# 

import unittest

def combine(iX, iY):
   results = iX << 11
   results += (iY & (pow(2,11) - 1))
   return results

def uncombine(iCombined):
   iX = iCombined / pow(2,11)
   iY = iCombined % pow(2,11)
   if iY >= pow(2,10):
      iY -= pow(2,11)
   
   return (iX, iY)

class CombineTest(unittest.TestCase):
   knownValues = (
               (-5, -13, -8205),
               (0, 0, 0),
               (1023, 1023, 2096127),
               )

   def testCombineKnownValues(self):
      for iX, iY, iExpected in self.knownValues:
         iActual = combine(iX, iY)
         self.assertTrue(iExpected == iActual,
                     "%d != %d" % (iExpected, iActual)
                     )

class UncombineTest(unittest.TestCase):
   knownValues = (
               (0, 0, 0),
               (-8205, -5, -13),
               (2096127, 1023, 1023),
               )

   def testUncombineKnownValues(self):
      for iCombined, iExpectedX, iExpectedY in self.knownValues:
         iActualX, iActualY = uncombine(iCombined)
         self.assertTrue(iExpectedX == iActualX,
                        "X: %d != %d" % (iExpectedX, iActualX))
         self.assertTrue(iExpectedY == iActualY,
                        "Y: %d != %d" % (iExpectedY, iActualY))


class SanityCheck(unittest.TestCase):
   def testSanity(self):
     for iStartX in range(-1023, 1023, 1):
       for iStartY in range(-1023, 1023, 1):
         iFinishX, iFinishY = uncombine(combine(iStartX, iStartY))
         self.assertTrue(iStartX == iFinishX,
                     "iStartX: %d, iFinishX: %d" % (iStartX, iFinishX))
         self.assertTrue(iStartY == iFinishY,
                     "iStartY: %d, iFinishY: %d" % (iStartY, iFinishY))
       

if __name__ == "__main__":
   unittest.main()
